package gotnames.dm;

import gotnames.Utils;

import java.util.logging.Logger;

import javax.jdo.PersistenceManager;
import javax.jdo.Transaction;

/**
 * Utility class for executing transactions. The method {@link #call()} is
 * implemented by the client code and it is called one or more times depending
 * on whether the transaction was aborted or not. The code must be idempotent
 * otherwise behavior is undefined. Call {@link #go()} to execute the
 * transaction.
 * <p>
 * 
 * If no object need be returned from the transaction use {@link Void}.
 */
public abstract class KTrans<X> {
	private static final Logger LOG = Utils.getLog();
	
	/** Extension of {@link KTrans} for transaction which do not return an object. */
	public abstract static class Void extends KTrans<java.lang.Void> {
		
		public Void(PersistenceManager pm) {
			super(pm);
		}

		/** Execute the transaction */
		protected abstract void run();
		
		@Override protected final java.lang.Void call() {
			run();
			return null;
		}
	}
	
	protected final PersistenceManager pm;
	
	public KTrans(PersistenceManager pm) {
		this.pm = pm;
	}
	
	/** @return the result of executing the transaction */
	protected abstract X call();
	
	/**
	 * The maximum number of times to attempt to execute the transaction. This
	 * should not be too low as that will result in transaction failures, but
	 * not too high since that can result in unacceptable latency.
	 */
	private static final int MAX_ATTEMPTS = 16;
	
	/** Execute the transaction */
	public X go() {
		long begin = System.nanoTime();
		int attemptNr = 1;
		while (true) {
			Transaction t = pm.currentTransaction();
			t.begin();
			try {
				X x = call();
				t.commit();
				LOG.finer("Executed transaction from " + Utils.myCaller() + " in " + Utils.elapsed(begin) + " on attempt " + attemptNr);
				return x;
			} catch (java.util.ConcurrentModificationException e) {
				// java.util.ConcurrentModificationException: too much contention on these datastore entities. please try again.
				if (attemptNr >= MAX_ATTEMPTS)
					throw e;
			} finally {
				if (t.isActive())
					t.rollback();
			}
			attemptNr++;
		}
	}
	
	/** Forward to {@link #call()}; used to execute inside another transaction */
	public X goNoTrans() {
		return call();
	}

}
